/*
		ServEngine framework library
		A library to fast develop JEE online applications
	
		Copyright 2008 José Ignacio de Córdoba Álvaro

		Licensed under the Apache License, Version 2.0 (the "License");
		you may not use this file except in compliance with the License.
		You may obtain a copy of the License at

				http://www.apache.org/licenses/LICENSE-2.0

		Unless required by applicable law or agreed to in writing, software
		distributed under the License is distributed on an "AS IS" BASIS,
		WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
		See the License for the specific language governing permissions and
		limitations under the License.
	
		Jose Ignacio de Cordoba Alvaro
		http://ignacio.decordoba.com
*/

package com.servengine.portal;

import com.servengine.user.ExtraUserdataField;
import com.servengine.user.Role;

import com.servengine.user.User;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;

import com.servengine.user.UserManagerLocal;

import java.util.ArrayList;

import java.util.Arrays;

import javax.mail.internet.InternetAddress;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

/**
 * This session EJB encapsulates all business logic related to portal management.
 * @see PortalManagerLocal
 */
@Stateless(name="PortalManager")
public class PortalManagerBean implements PortalManagerLocal
{
	private static org.apache.commons.logging.Log log =org.apache.commons.logging.LogFactory.getLog(PortalManagerBean.class.getName());

	@PersistenceContext(unitName="mainPersistenceUnit")
	private EntityManager entityManager;

	@EJB private UserManagerLocal userManager;

	public static String[] SYSTEM_SERVICE_NAMES = {"","global","user","portal","log","util"};
	//	Set<String> nonSystemServiceNames;

	public Collection<Portal> getPortalsBySearchstring(String searchstring)
	{
			if (searchstring == null || searchstring.length() == 0)
				throw new IllegalArgumentException("Shouldn't find all portals. Please suply a search string");
		  Query query = entityManager.createNamedQuery("Portal.findBySearchstring");
		  query.setParameter("searchstring",searchstring);
		  return query.getResultList();
	}

	public long getPortalCount()
	{
		return (Long)entityManager.createNamedQuery("Portal.getPortalCount").getSingleResult();
	}

	public Portal getPortal(String portalid)
	{
		return entityManager.find(Portal.class,portalid);
	}

	private void enableAction(Portal portal, Component action)
	{
		Set<Component> actions = new HashSet<Component>();
		actions.addAll(portal.getActions()); //Por el contains
	  if (!actions.contains(action))
		{
			actions.add(action);
			portal.setActions(actions);
			entityManager.persist(portal);
		}
	  if (action.isAdmin())// Suponemos que es un Componente de administración, así que le ponemos el role de administración.
	  {
	    Query query = entityManager.createNamedQuery("Role.findByName");
	    query.setParameter("portalid",portal.getId());
	    query.setParameter("name","admin");//FIXME
			Role adminRole = (Role)query.getSingleResult();
	    Set<Component> adminRoleActions = new HashSet<Component>();
	    adminRoleActions.addAll(adminRole.getActions()); // El CMR solo va en sentido role -> components. La vida es asi, chico.
	    if (!adminRoleActions.contains(action))
	    {
	      adminRoleActions.add(action);
	      adminRole.setActions(adminRoleActions);
	    }
			entityManager.persist(adminRole);
	  } else if (!action.isGuest()) // Si es un cliente de usuario no administrador.
		{
			Query query = entityManager.createNamedQuery("Role.findByName");
			query.setParameter("portalid",portal.getId());
	    query.setParameter("name","user");//FIXME
			Role userRole = (Role)query.getSingleResult();
			Set<Component> userRoleActions = new HashSet<Component>();
			userRoleActions.addAll(userRole.getActions()); // El CMR solo va en sentido role -> components. La vida es asi, chico.
			if (!userRoleActions.contains(action))
			{
				userRoleActions.add(action);
				userRole.setActions(userRoleActions);
			}
			entityManager.persist(userRole);
	  }
		return;
	}

/*	private void disableAction(Portal portal, String className)
	{
		Set<Component> actions = new HashSet<Component>();
		for (Component action: portal.getActions())
			if (!action.getClassName().equals(className))
				actions.add(action);
		portal.setActions(actions);

		Component action = entityManager.find(Component.class,className);
		for (Role role: portal.getRoles())
		{
		// Miramos si el role no tiene el componente. Si lo tiene, lo borramos.
			Set<Component> rolenewcomponents = new HashSet<Component>();
			rolenewcomponents.addAll(role.getActions());
			if (rolenewcomponents.contains(action))
			{
				rolenewcomponents.remove(action);
				role.setActions(rolenewcomponents);
			}
		}
		return;
	}*/

	public boolean exists(String portalid)
	{
		return entityManager.find(Portal.class,portalid) != null;
	}

	public void createPortal(java.lang.String portalid, String homeurl, java.lang.String description, String locale,
		String adminuserid, String adminpassword, String adminemail, String adminfirstname, String adminlastname,
		String adminlastname2)
	{
		Portal portal = new Portal(portalid,homeurl,description,locale);
		entityManager.persist(portal);
	  portal.setUserSelfSignup(true);
	  portal.setNewUserNotification(true);
		userManager.createAdminUser(portal, adminuserid, adminpassword, adminfirstname, adminlastname, adminlastname2, adminemail);
//		userManager.persist(new User(portal,"guest"),null);
		entityManager.flush();
		log.info("Portal created: " + portalid + ", adminuserid: " + adminuserid);
	}

	public void checkPortal(String portalid, String locale, boolean addAllActionsIfNew)
	{
	  Portal portal = getPortal(portalid);
		if (portal==null)
		{
			portal = new Portal(portalid, locale); //Creamos el portal
			portal.setUserSelfSignup(false); // No new users in this portals! // FIXME verificar que esto va.
			portal.setNewUserNotification(true); // Just in case
			entityManager.persist(portal);
		}
	  Query query = entityManager.createNamedQuery("Role.findByName");
	  query.setParameter("portalid",portal.getId());
	  query.setParameter("name","admin");//FIXME
	  try
		{
			query.getSingleResult();
		} catch (NoResultException e)
		{
			Role role = new Role(portal,"admin");
			entityManager.persist(role);
			portal.getRoles().add(role);
		}
	  query = entityManager.createNamedQuery("Role.findByName");
	  query.setParameter("portalid",portal.getId());
	  query.setParameter("name","user");//FIXME
	   try
	   {
	     query.getSingleResult();
	   } catch (NoResultException e)
	   {
				Role role = new Role(portal,"user",true);
	     entityManager.persist(role);
			 portal.getRoles().add(role);
	   }

		if (addAllActionsIfNew && (portal.getActions()==null || portal.getActions().size()==0))
		  for (Component action: (List<Component>)entityManager.createNamedQuery("Component.findAll").getResultList())
		    enableAction(portal,action);
		else //Solo añadimos las acciones de 
		{
			for (Component action: getActionsForServiceName("user"))
				enableAction(portal,action);
			for (Component action: getActionsForServiceName("portal"))
				enableAction(portal,action);
		}
		if (userManager.getRole(portal.getId(),portal.getAdminRoleName()).getUserCount()==0)
		  userManager.createAdminUsers(portal);
	}

	public void removePortal(String portalid)
	{
		Portal portal = getPortal(portalid);
		if (portal.getId().equals("tuportal.com") || portal.getId().equals("skios") || portal.getId().equals("servengine") || portal.getId().equals("generic"))
			throw new IllegalArgumentException("¡¡¡Tu estas tarao!!!"); // FIXME
		entityManager.remove(portal);
	}

	public Portal persist(Portal portal)
	{
		if (exists(portal.getId()))
			return entityManager.merge(portal);
		entityManager.persist(portal);
		return portal;
	}

	public List<Portal> getAllPortals()
	{
		return entityManager.createNamedQuery("Portal.findAll").getResultList();
	}

	public void portalAccessed(String portalid)
	{
		getPortal(portalid).setLastAccessed(new java.util.Date());
	}

	public Portal setProperty(String portalid, String propertyid, String value)
	{
	  if (value==null || value.length()==0)
	    return removeProperty(portalid,propertyid);
		Portal portal = getPortal(portalid);
		portal.setProperty(propertyid, value);
		return portal;
	}
/*
	public String getProperty(String portalid, String propertyid)
	{
		return propertymanagerlocal.getProperty(portalid, propertyid);
	}*/

	public Portal removeProperty(String portalid, String propertyname)
	{
		Portal portal = getPortal(portalid);
		Property expurganda = null;
		for (Property property: portal.getProperties())
			if (property.getName().equals(propertyname))
				expurganda = property;
		if (expurganda!=null)
		{
			portal.getProperties().remove(expurganda);
			entityManager.remove(expurganda);
		}
		return portal;
	}

/*	private UserManagerLocal getUserManagerLocal()
	{
		try
		{
			return (UserManagerLocal)new InitialContext().lookup("UserManager/local");
		} catch (Throwable e)
		{
			throw new IllegalStateException(e.getMessage());
		}
	}
*/
/*	public Integer getIntegerproperty(String portalid, String propertyid)
	{
		String property = getProperty(portalid, propertyid);
		try
		{
			if (property != null)
				return new Integer(property);
		} catch (NumberFormatException e)
		{
			if (log.isDebugEnabled())
				log.debug(property + " for " + propertyid + "@" + portalid + " can't be used as an Integer. Returning null");
			return null;
		}
		return null;
	}

	public int getIntproperty(String portalid, String propertyid)
	{
		String property = getProperty(portalid, propertyid);
		try
		{
			if (property != null)
				return Integer.parseInt(property);
		} catch (NumberFormatException e)
		{
			if (log.isDebugEnabled())
				log.debug(property + " for " + propertyid + "@" + portalid + " can't be used as an Integer. Returning null");
			return 0;
		}
		return 0;
	}*/

/*	public PortalSBean enableservice(String portalid, String servicename) throws FinderException
	{
		PortalLocal portal = portallocalhome.findByPrimaryKey(portalid);
		for (ComponentLocal component: componentlocalhome.findAll())
			if (component.getServiceName().equals(servicename))
				enablecomponent(portal, component);
		return getPortal(portalid);
	}*/

	public Portal getPortalByHomeURL(String homeurl)
	{
		Query query = entityManager.createNamedQuery("Portal.findByHomeurl");
		query.setParameter("homeurl",homeurl);
		try
		{
			return  (Portal)query.getSingleResult();
		} catch (NoResultException e)
		{
			query = entityManager.createNamedQuery("Portal.findByHomeurl");
			query.setParameter("homeurl",homeurl+"/");
			return (Portal)query.getSingleResult();
		}
	}

/*	public Collection getDisabledcomponents(String portalid) throws FinderException
	{
		Collection components = new ArrayList(); //Para los ComponentSBean
		Collection enabledcomponents = portallocalhome.findByPrimaryKey(portalid).getComponents();
		Iterator i = componentlocalhome.findAll().iterator(); // Todos
		while (i.hasNext())
		{
			Object component = i.next();
			if (!enabledcomponents.contains(component))
				components.add(new ComponentSBean((ComponentLocal)component));
		}
		return components;
	}

	public ComponentSBean getComponent(String classname) throws FinderException
	{
		return new ComponentSBean(componentlocalhome.findByPrimaryKey(classname));
	}*/

/*	public Collection<String> getActiveActions(String portalid) throws FinderException
	{
		Collection<String> actions = new ArrayList<String>();
		for (ComponentLocal component: portallocalhome.findByPrimaryKey(portalid).getComponents())
			actions.add(component.getClassname());
		return actions;
	}*/

/*	public Collection<ComponentSBean> getAllcomponents() throws FinderException
	{
		Collection<ComponentSBean> components = new ArrayList<ComponentSBean>();
		for (ComponentLocal component: componentlocalhome.findAll())
			//      if (!component.getServicename().equals("system") && !component.getServicename().equals("admin"));
			components.add(new ComponentSBean(component));
		return components;
	}*/

	public void removeActionRoles(String portalid, Component action)
	{
		Portal portal = getPortal(portalid);
		for (Role role: portal.getRoles())
		{
			Set<Component> components = new HashSet<Component>();
			components.addAll(role.getActions());
			if (components.contains(action))
			{
				components.remove(action);
				role.setActions(components);
			}
		}
	}

	public List<Role> getActionRoles(String portalid, String className)
	{
	  Query query = entityManager.createNamedQuery("Role.findByClassName");
	  query.setParameter("portalid",portalid);
	  query.setParameter("className",className);
	  return query.getResultList();
	}

/*	private void setActionRoles(String portalid, Component action, Integer[] srolepkeys)
	{
	  Portal portal = getPortal(portalid);
		HashSet rolepkeys = new HashSet(); // Pasamos a un arraylist para poder usar "contains()".
		for (int i = 0; i < srolepkeys.length; i++)
			rolepkeys.add(srolepkeys[i]); 
		for (Role role: portal.getRoles())
		{
			if (rolepkeys.contains(role.getId()))
			{ // Miramos si el role ya tiene el component. Si no, lo añadimos.
				Set<Component> roleActions = new HashSet<Component>();
				roleActions.addAll(role.getActions());
				if (!roleActions.contains(action))
				{
					roleActions.add(action);
					role.setActions(roleActions);
				}
			} else // Miramos si el role no tiene el componente. Si lo tiene, lo borramos.
			{
				Set<Component> roleNewActions = new HashSet<Component>();
				roleNewActions.addAll(role.getActions());
				if (roleNewActions.contains(action))
				{
					roleNewActions.remove(action);
					role.setActions(roleNewActions);
				}
			}
		}
	}*/

/*	public int getRolecomponentcount(Integer rolepkey) throws FinderException
	{
		return rolelocalhome.findByPrimaryKey(rolepkey).getComponents().size();
	}*/

/*	public int getRoleusercount(Integer rolepkey) throws FinderException
	{
		return rolelocalhome.findByPrimaryKey(rolepkey).getUsers().size();
	}*/

/*	public Collection<String> getActionsByRole(Integer rolepkey) throws FinderException
	{
		Collection<String> components = new ArrayList<String>();
		for (ComponentLocal component: rolelocalhome.findByPrimaryKey(rolepkey).getComponents())
			components.add(component.getClassname());
		return components;
	}*/

	//@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
 public Component getAction(String className)
 {
	 return entityManager.find(Component.class,className);
 }

/* public Component getAction(String className, boolean guest, boolean admin)
 {
	 Component action = entityManager.find(Component.class,className);
	 if (action==null)
	 {
		 action = new Component(className,guest,admin);
		 entityManager.persist(action);
	 }
		return action;
 }*/
 
 public void persist(Component action)
 {
	 if (entityManager.find(Component.class,action.getClassName())==null)
		entityManager.persist(action);
	 else 
		entityManager.merge(action);
 }
 
 public Set<Portal> getPortalsWithPropertyName(String propertyName)
 {
	 Set<Portal> portals = new HashSet<Portal>();
	 Query query = entityManager.createNamedQuery("Property.findByName");
	 query.setParameter("name",propertyName);
	 for (Property property: (List<Property>)query.getResultList())
		 portals.add(property.getPortal());
	 return portals;
 }
	public Set<Portal> getPortalsWithProperty(String propertyName, String propertyValue)
	{
		Set<Portal> portals = new HashSet<Portal>();
		Query query = entityManager.createNamedQuery("Property.findByNameAndValue");
		query.setParameter("name",propertyName);
	  query.setParameter("value",propertyValue);
		for (Property property: (List<Property>)query.getResultList())
			portals.add(property.getPortal());
		return portals;
	}
	public InternetAddress[] getAdminEmails(String portalid)
	{
		Collection<User> admins = userManager.getRole(portalid,"admin").getUsers();
		ArrayList emails = new ArrayList();
		for (User user: admins)
		{
			try
			{
				emails.add(new InternetAddress(user.getEmail()));
			} catch (Exception e)
			{
				log.debug("Portal " + portalid + " has an incorrect admin user email for " + user.getUserid() + ": " + e + "/" + e.getMessage());
			}
		}
		InternetAddress[] array = new InternetAddress[emails.size()];
		return (InternetAddress[])emails.toArray(array);
	}
	public InternetAddress getEmail(String portalid)
	{
		Portal portal = entityManager.find(Portal.class,portalid);
		String portalemail = portal.getProperty(Portal.EMAIL_PROPERTY_NAME);
		if (portalemail != null && portalemail.length() > 0)
			try
			{
				return new InternetAddress(portalemail);
				//return new InternetAddress(portalemail, portalid);
			} catch (Exception e)
			{
				log.error(e.getMessage(), e);
			}
		InternetAddress[] adminemails = getAdminEmails(portalid);
		if (adminemails != null && adminemails.length > 0)
		{
			if (log.isDebugEnabled())
				log.debug("Returning admin email "+adminemails[0]+" for "+portalid);
			return adminemails[0];
		}
		log.warn("Could not find any admin email for portal "+portalid+". Returning null.");
		return null;
	}
	public Set<Component> getActionsForServiceName(String servicename)
	{
		Set<Component> actions = new HashSet<Component>();
		for (Component action: (List<Component>)entityManager.createNamedQuery("Component.findAll").getResultList())
			if (action.getServiceName().equals(servicename))
				actions.add(action);
		return actions;
	}

	public void updateRoleAdminServiceActions(Role role)
	{
		if (role.getAdminServices()!=null)
		{		
			HashSet<Component> actions = new HashSet<Component>();
			for (Component action: role.getActions())
				if (!action.isAdmin())
					actions.add(action);
			for (String adminService: role.getAdminServices())
				for (Component adminAction: getActionsForServiceName(adminService))
					if (adminAction.isAdmin())
						actions.add(adminAction);
			role.setActions(actions);
			entityManager.merge(role);
		}
	}

	public void updateRoleServiceActions(Role role)
	{
		if (role.getServices()!=null) //Si es null, no hacemos nada, para no desactivar todas las acciones para este role.
		{   
			HashSet<Component> actions = new HashSet<Component>();
			for (Component action: role.getActions())
				if (action.isAdmin())
					actions.add(action);
			for (String adminService: role.getAdminServices())
				for (Component adminAction: getActionsForServiceName(adminService))
					if (!adminAction.isAdmin())
						actions.add(adminAction);
			role.setActions(actions);
			entityManager.merge(role);
		}
	}


/*	public void disableService(Portal portal, String serviceName) throws javax.ejb.FinderException, RemoteException, CreateException
	{
		if (!getServletContextUtils().getSystemServiceNames().contains(serviceName))
			for (Component action: getServletContextUtils().getUserActionsForServiceName(serviceName))
				{
					//portalmanager.disablecomponent(portal.getId(),action.getClassName());
					portal.removeAction(action.getClassName());
				}
		else
			throw new IllegalArgumentException();
	}
	 public void enableService(Portal portal, String serviceName) throws javax.ejb.FinderException, RemoteException, CreateException
	 {
	//     if (!getServletContextUtils().getSystemServiceNames().contains(serviceName))
			 for (Component action: getServletContextUtils().getUserActionsForServiceName(serviceName))
				 {
					 //portalmanager.enablecomponent(portal.getId(),action);
					 portal.addAction(action);
				 }
	//     else
	//       throw new IllegalArgumentException();
	 }
*/
 public void removeExtraUserdataFields(String portalid, Integer[] fieldIds)
 {
//	 Portal portal = getPortal(portalid);
	 for (int i = 0; i < fieldIds.length; i++)
	 {
		 ExtraUserdataField field = entityManager.find(ExtraUserdataField.class,fieldIds[i]);
//		 if (!field.getPortal().equals(portal))
		 if (!field.getPortal().getId().equals(portalid))
			throw new IllegalArgumentException("Look at my friend... trying to erase other guys's datafields... bad bad bad boy...");
		//Puta mierda Hibernate ¿y el cascade?
	   /*Collection<ExtraUserdata> expurganda = new ArrayList<ExtraUserdata>();
	       expurganda.addAll(field.getData());
	       for (ExtraUserdata datum: expurganda)
	       {
					 datum.setUser(null);
					 datum.setField(null);
				   field.getData().remove(datum);
				   entityManager.remove(datum);
				 }
	       entityManager.flush();*/
//			field.setPortal(null);
//		portal.getExtraUserdataFields().remove(field);
		 entityManager.remove(field);
	   //entityManager.flush();
		 //entityManager.refresh(portal);
	 }
//	 return portal;
 }
	public List<String> getSystemServiceNames()
	{
		return Arrays.asList(SYSTEM_SERVICE_NAMES);
	}
	public Set<String> getNonSystemServiceNames()
	{
	  Set<String> nonSystemServiceNames = new HashSet<String>();
	  for (Component action: (List<Component>)entityManager.createNamedQuery("Component.findAll").getResultList())
	    if (!Arrays.asList(SYSTEM_SERVICE_NAMES).contains(action.getServiceName()))
	      nonSystemServiceNames.add(action.getServiceName());
		return nonSystemServiceNames;
	}
	
/*	public List<String> getServiceNames(Collection<String> classNames)
	{
		List<String> serviceNames = new ArrayList<String>();
		for (String className: classNames)
		{
			try
			{
				String serviceName = getAction(className).getServiceName();
				if (!systemServiceNames.contains(serviceName) && !serviceNames.contains(serviceName))
	//        if (!serviceNames.contains(serviceName))
					serviceNames.add(serviceName);
			} catch (NullPointerException e)
			{
				log.error(className+": "+e.getMessage());
			}
		}
		return serviceNames;
	}*/
	public Set<Component> getActionsByRoleId(String portalid, Integer id)
	{
		Role role = entityManager.find(Role.class,id);
		if (!role.getPortal().getId().equals(portalid))
			throw new IllegalArgumentException();
		return role.getActions();
	}
	public void setActionRoles(String portalid, String className, Integer[] roleIds)
	{
		Component action = entityManager.find(Component.class,className);
		Portal portal = entityManager.find(Portal.class,portalid);
		List<Integer> ids = roleIds==null?new ArrayList<Integer>():Arrays.asList(roleIds);
		for (Role role: portal.getRoles())
			if (ids.contains(role.getId()))
				role.getActions().add(action);
			else
				role.getActions().remove(action);
	}
	public Integer[] getActionRoleIdsForPortal(Portal portal, Component action)
	{
	  Set<Integer> roleIds = new HashSet<Integer>();
		for (Role role: portal.getRoles())
			for (Component tmpAction: entityManager.find(Role.class,role.getId()).getActions()) //FIXME KK de session. Debo re-buscar el role
	      if (tmpAction.equals(action))
	  //      if (role.getActions().contains(action)) // FIXME por qué no funciona contains y tengo que hacer este for?????
	        roleIds.add(role.getId());
		return roleIds.toArray(new Integer[roleIds.size()]);
	}

/*	public Map<String,User> getGuestUsers()
	{
		Map<String,User> guestUsers = new HashMap<String,User>();
		for (Portal portal: getAllPortals())
		{
		  Query query = entityManager.createNamedQuery("User.findByUserid");
		  query.setParameter("portalid",portal.getId());
		  query.setParameter("userid",UserManagerBean.guestUserid);
		  try
		  {
		    guestUsers.put(portal.getId(),(User)query.getSingleResult());
		  }
		  catch (NoResultException e)
		  {
		    log.warn("FIX THIS: No guest user in portal "+portal.getId());
		    guestUsers.put(portal.getId(),new User(entityManager.find(Portal.class,portal.getId()),"guest"));
		  }
		}
		return guestUsers;
	}*/
	public List<Component> getAllActions()
	{
	  return entityManager.createNamedQuery("Component.findAll").getResultList();
	}
}
